//MegaMek - Copyright (C) 2000,2001,2002,2003,2004 Ben Mazur (bmazur@sev.org)

package megamek.common.actions;

import java.util.Enumeration;

import megamek.common.AmmoType;
import megamek.common.BattleArmor;
import megamek.common.Entity;
import megamek.common.Infantry;
import megamek.common.IGame;
import megamek.common.Mech;
import megamek.common.Mounted;
import megamek.common.PlanetaryConditions;
import megamek.common.Targetable;
import megamek.common.ToHitData;
import megamek.common.WeaponType;

// TODO: Auto-generated Javadoc
/**
 * Abstract superclass for any action where an entity is attacking another
 * entity.
 */
public abstract class AbstractAttackAction extends AbstractEntityAction
        implements AttackAction {
    /** The Constant serialVersionUID. */
    private static final long serialVersionUID = -897197664652217134L;
    /** The target type. */
    private int targetType;
    /** The target id. */
    private int targetId;

    /**
     *  default to attacking an entity, since this is what most of them are.
     *
     * @param entityId the entity id
     * @param targetId the target id
     */
    public AbstractAttackAction(final int entityId, final int targetId) {
        super(entityId);
        this.targetType = Targetable.TYPE_ENTITY;
        this.targetId = targetId;
    }

    /**
     * Instantiates a new abstract attack action.
     *
     * @param entityId the entity id
     * @param targetType the target type
     * @param targetId the target id
     */
    public AbstractAttackAction(final int entityId, final int targetType, final int targetId) {
        super(entityId);
        this.targetType = targetType;
        this.targetId = targetId;
    }

    /**
     * @see megamek.common.actions.AttackAction#getTargetType()
     * @return targetType
     */
    public final int getTargetType() {
        return targetType;
    }

    /**
     * @see megamek.common.actions.AttackAction#getTargetId()
     * @return targetId
     */
    public final int getTargetId() {
        return targetId;
    }

    /**
     * @see megamek.common.actions.AttackAction#setTargetType(int)
     * @param targetType
     */
    public final void setTargetType(final int targetType) {
        this.targetType = targetType;
    }

    /**
     * @see megamek.common.actions.AttackAction#setTargetId(int)
     * @param targetId
     */
    public final void setTargetId(final int targetId) {
        this.targetId = targetId;
    }

    /**
     * Gets the target.
     *
     * @param game the game
     * @return the target
     */
    public final Targetable getTarget(final IGame game) {
        return game.getTarget(getTargetType(), getTargetId());
    }

    /**
     * Gets the entity.
     *
     * @param game the game
     * @return the entity
     */
    public final Entity getEntity(final IGame game) {
        Entity oEntity = game.getEntity(getEntityId());
        // if we have an artyattack, we might need to get an out-of-game entity
        // if it died or fled
        if (oEntity == null) {
            oEntity = game.getOutOfGameEntity(getEntityId());
        }
        return oEntity;
    }

    /**
     * used by the toHit of derived classes atype may be null if not using an
     * ammo based weapon.
     *
     * @param game the game
     * @param target the target
     * @param atype the atype
     * @param attacker the attacker
     * @param isWeapon the is weapon
     * @return the to hit data
     */
    public static ToHitData nightModifiers(final IGame game, final Targetable target,
            final AmmoType atype, final Entity attacker, final boolean isWeapon) {
        ToHitData toHit = null;
        Entity tEntity = null;
        if (target.getTargetType() == Targetable.TYPE_ENTITY) {
            tEntity = (Entity) target;
        }
        toHit = new ToHitData();

        int lightCond = game.getPlanetaryConditions().getLight();
        if (lightCond == PlanetaryConditions.L_DAY) {
            //not night so just return
            return toHit;
        }
        // The base night penalty
        int nightModifier = 0;
        nightModifier = game.getPlanetaryConditions().getLightHitPenalty(isWeapon);
        toHit.addModifier(nightModifier, game.getPlanetaryConditions().getLightCurrentName());

        boolean illuminated = false;
        if (tEntity != null) {
            illuminated = tEntity.isIlluminated();
            // hack for unresolved actions so client shows right BTH
            if (!illuminated) {
                for (Enumeration<EntityAction> uActions = game.getActions(); uActions
                        .hasMoreElements();) {
                    EntityAction action = uActions.nextElement();
                    if (action instanceof SearchlightAttackAction) {
                        SearchlightAttackAction saa = (SearchlightAttackAction) action;
                        if (saa.willIlluminate(game, tEntity)) {
                            illuminated = true;
                            break;
                        }
                    }
                }
            }
        }

        nightModifier = searchlightsAndFlares(game, target, atype, toHit,
				tEntity, lightCond, nightModifier, illuminated);

        laserHeatsinks(game, toHit, tEntity, nightModifier);

        //now check for general hit bonuses for heat
        if (tEntity != null && !(attacker instanceof Infantry && !(attacker instanceof BattleArmor))) {
            int heatBonus = game.getPlanetaryConditions().getLightHeatBonus(tEntity.heat);
            if (heatBonus < 0) {
                toHit.addModifier(heatBonus, "target excess heat at night");
            }
        }
        return toHit;
    }

	/**
	 * Laser heatsinks.
	 *
	 * @param game the game
	 * @param toHit the to hit
	 * @param tEntity the t entity
	 * @param nightModifier the night modifier
	 */
	private static void laserHeatsinks(final IGame game, final ToHitData toHit,
			final Entity tEntity, int nightModifier) {

        if (nightModifier > 0 && tEntity != null && tEntity instanceof Mech
                && ((Mech) tEntity).hasLaserHeatSinks()) {
            boolean lhsused = false;
            if (tEntity.heat > 0) {
                toHit.addModifier(-nightModifier, "target overheated with laser heatsinks");
                nightModifier = 0;
            }
            // actions that generate heat give a -1 modifier
            else if (tEntity.heatBuildup > 0 || tEntity.isStealthActive()) {
                lhsused = true;
            } else {
                // Can't check weapons fired by the target because isUsedThisRound()
                // is not valid if the attacker declared first.
                // therefore, enumerate WeaponAttackActions
                for (Enumeration<EntityAction> wActions = game.getActions(); wActions
                        .hasMoreElements();) {
                    EntityAction action = wActions.nextElement();
                    if (action instanceof WeaponAttackAction) {
                        WeaponAttackAction waa = (WeaponAttackAction) action;
                        if (waa.getEntityId() == tEntity.getId()) {
                            Mounted weapon = tEntity.getEquipment(waa.getWeaponId());
                            WeaponType wtype = (WeaponType) weapon.getType();
                            if (wtype.getHeat() != 0 || weapon.isRapidfire()) {
                                // target fired a weapon that generates heat
                                lhsused = true;
                                break;
                            }
                        }
                    }
                }
            }
            if (lhsused) {
                toHit.addModifier(-1, "target uses laser heatsinks");
            }
        }
	}

	/**
	 * Searchlights and flares.
	 *
	 * @param game the game
	 * @param target the target
	 * @param atype the atype
	 * @param toHit the to hit
	 * @param tEntity the t entity
	 * @param lightCond the light cond
	 * @param nightModifier the night modifier
	 * @param illuminated the illuminated
	 * @return the int
	 */
	private static int searchlightsAndFlares(final IGame game, final Targetable target,
			final AmmoType atype, final ToHitData toHit, final Entity tEntity, final int lightCond,
			int nightModifier, final boolean illuminated) {

		// Searchlights reduce the penalty to 0 or 1 for pitch-black, except for dusk/dawn
        int searchlightMod = Math.min(3, nightModifier);
        if (tEntity != null && lightCond > PlanetaryConditions.L_DUSK) {
            if (tEntity.isUsingSpotlight()) {
                toHit.addModifier(-searchlightMod, "target using searchlight");
                nightModifier = nightModifier - searchlightMod;
            } else if (illuminated) {
                toHit.addModifier(-searchlightMod, "target illuminated by searchlight");
                nightModifier = nightModifier - searchlightMod;
            }
        }
        // So do flares
        else if (game.isPositionIlluminated(target.getPosition())) {
            toHit.addModifier(-nightModifier, "target illuminated by flare");
            nightModifier = 0;
        } else if (atype != null
				&& (atype.getAmmoType() == AmmoType.T_AC || atype
						.getAmmoType() == AmmoType.T_LAC)
				&& (atype.getMunitionType() == AmmoType.M_INCENDIARY_AC || atype
						.getMunitionType() == AmmoType.M_TRACER)) {
			toHit.addModifier(-1, "incendiary/tracer ammo");
			nightModifier--;
		}
		return nightModifier;
	}

}
